perm filename INITEX.CH[TEX,DEK]1 blob sn#680527 filedate 1982-10-16 generic text, type C, neo UTF8
COMMENT ⊗   VALID 00044 PAGES
C REC  PAGE   DESCRIPTION
C00001 00001
C00007 00002	The |banner| string defined here should be changed whenever \TeX\
C00008 00003	@ Some of the code below is intended to be used only when diagnosing the
C00010 00004	@ This program has two important variations: (1) There is a long and slow
C00012 00005	@<Compiler directives@>=
C00013 00006	@ The following parameters can be changed at compile time to extend or
C00017 00007	One can't simply make helter-skelter changes to the following constants,
C00019 00008	@ The ascii code is ``standard'' only to a certain extent, since many
C00021 00009	@p function a_open_in(var f:alpha_file):boolean
C00024 00010	@ Input from text files is read one line at a time, using a routine called
C00027 00011	@p function input_ln(var f:alpha_file):boolean {inputs the next line
C00030 00012	@ The following program does the required initialization
C00035 00013	@<Character |k| cannot be printed@>=
C00036 00014	@p procedure term_input {gets a line from the terminal}
C00037 00015	@ It is desirable to provide an `\.E' option here that gives the user
C00040 00016	It is usually most efficient to have |min_quarterword=min_halfword=0|,
C00041 00017	The inner loop of \TeX\ will run faster with respect to compilers
C00043 00018	ch_code(carriage_return)←car_ret ch_code(" ")←spacer ch_code("\")←escape
C00045 00019	@ The following procedure, which is called just before \TeX\ initializes its
C00046 00020	The global variable |line| contains the line number in the topmost
C00048 00021	@<Print location of current line@>=
C00049 00022	@ The |begin_file_reading| procedure starts a new level of input for lines
C00051 00023	@ Conversely, the variables must be downdated when such a level of input
C00052 00024	@ If the user has set the |pausing| parameter to some nonzero value,
C00054 00025	@ The first line of a file must be treated specially, since |input_ln|
C00056 00026	@ The file names we shall deal with have the following structure:
C00058 00027	@ Input files that can't be found in the user's area may appear in a standard
C00059 00028	@ And here's the second.
C00060 00029	@ The third.
C00061 00030	@ Conversely, here is a routine that takes three strings and prints a file
C00062 00031	@ Another system-dependent routine is needed to convert three \TeX\ strings
C00065 00032	@ A messier routine is also needed, since format file names must be scanned
C00066 00033	@ @<Set init...@>=
C00067 00034	@ Here is the messy routine that was just mentioned. It sets |name_of_file|
C00071 00035	@ Operating systems often make it possible to determine the exact name (and
C00073 00036	@ Here we have to remember that the |input_ln| routine
C00076 00037	@!dvi_index=0..dvi_buf_size {an index into the output buffer}
C00077 00038	@ Some systems may find it more efficient to make |dvi_buf| a |packed|
C00078 00039	@ The actual output of |dvi_buf[a..b]| to |dvi_file| is performed by calling
C00080 00040	@<Finish the \.{DVI} file@>=
C00083 00041	@ @<Finish issuing a diagnostic message for an overfull or underfull hbox@>=
C00084 00042	@ @<Finish issuing a diagnostic message for an overfull or underfull vbox@>=
C00085 00043	@ Here we do whatever is needed to complete \TeX's job gracefully
C00087 00044	@* \[54] System-dependent changes.
C00092 ENDMK
C⊗;
The |banner| string defined here should be changed whenever \TeX\
undergoes any modifications, so that it will be clear which version of
\TeX\ might be the guilty party when a problem arises.
@↑extensions to \TeX@>

@d banner=='This is TeX, Version 0.3S' {printed when \TeX\ starts}
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Some of the code below is intended to be used only when diagnosing the
strange behavior that sometimes occurs when \TeX\ is being installed or
when system wizards are fooling around with \TeX\ without quite knowing
what they are doing. Such code will not normally be compiled; it is
delimited by the codewords `$|debug|\ldotsm|gubed|$', with apologies
to people who wish to preserve the purity of English. Similarly, there
is some conditional code delimited by `$|stat|\ldotsm|tats|$'
that is intended only for use when statistics
are to be kept about \TeX's memory usage.

@d debug==@{ {change this to `$\\{debug}\eqv\null$' when debugging}
@d gubed==@t@>@} {change this to `$\\{gubed}\eqv\null$' when debugging}
@f debug==begin
@f gubed==end
@#
@d stat== {change this to `$\\{stat}\eqv\.{@@\{}$' when not
	gathering usage statistics}
@d tats== {change this to `$\\{tats}\eqv\.{@@\}}$' when not
	gathering usage statistics}
@f stat==begin
@f tats==end
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ This program has two important variations: (1) There is a long and slow
version called \.{INITEX}, which does the extra calculations need to
@.INITEX@>
initialize \TeX's internal tables; and (2)@@there is a shorter and faster
production version, which cuts the initialization to a bare minimum.
Parts of the program that are needed in (1) but not in (2) are delimited by
the codewords `$|init|\ldotsm|tini|$'.

(Here we are not really changing \.{TEX.WEB}; this module is included in the
change file so that \.{INITEX.CH} and \.{TEX.CH} have the same basic structure.)

@d init== {change this to `$\\{init}\eqv\.{@@\{}$' in the production version}
@d tini== {change this to `$\\{tini}\eqv\.{@@\}}$' in the production version}
@f init==begin
@f tini==end

@<Initialize whatever...@>=
@<Set initial values of key variables@>@/
init @<Initialize table entries (done by \.{INITEX} only)@>@;@+tini
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@<Compiler directives@>=
@{@&$C-,A+,D-,W+@}
	{no range check, catch arithmetic overflow, no debug overhead}
debug @{@&$C+,D:5,W+@}@+ gubed {but turn everything on when debugging}
{the `|W+|' switch catches more syntax errors}
{the `|D:5|' avoids initial stop for the debugger}
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ The following parameters can be changed at compile time to extend or
reduce \TeX's capacity. They may have different values in \.{INITEX} and
in production versions of \TeX.
@.INITEX@>
@↑system dependencies@>

@<Constants...@>=
@!mem_max=40000; {greatest index in \TeX's internal |mem| array,
	must be strictly less than |max_halfword|}
@!buf_size=500; {maximum number of characters simultaneously present in
	current lines of open files}
@!error_line=80; {width of context lines on terminal error messages}
@!half_error_line=50; {width of first lines of contexts in terminal
	error messages, should be between 30 and |error_line-15|}
@!max_print_line=80; {width of longest text lines output, should be at least 60}
@!stack_size=80; {maximum number of simultaneous input sources}
@!max_in_open=6; {maximum number of input files and error insertions that
	can be going on simultaneously}
@!font_max=100;
	{maximum internal font number, must not exceed |max_quarterword|}
@!bad_font_code=300; {user font codes must be less than this}
@!font_mem_size=20000; {number of words of |font_info| for all fonts}
@!param_size=30; {maximum number of simultaneous macro parameters}
@!nest_size=40; {maximum number of semantic levels simultaneously active}
@!max_strings=3000; {maximum number of strings}
@!string_vacancies=8000; {the minimum number of characters that should be
	available for the user's control sequences and font names,
	after \TeX's own error messages are stored}
@!pool_size=31000; {maximum number of characters in strings, including all
	error messages and help texts, and the names of all fonts and
	control sequences; must exceed |string_vacancies| by the total
	length of \TeX's own strings, which is currently about 22000}
@!save_size=300; {space for saving values outside of current group, must be
	at most |max_halfword|}
@!trie_size=8000; {space for hyphenation patterns, should be larger for
	\.{INITEX} than it is in production versions of \TeX}
@!dvi_buf_size=800; {size of the output buffer, must be a multiple of 8}
@!file_name_size=23; {file names shouldn't be longer than this}
@!pool_name='TEX.POOL[TEX,SYS]      ';
	{string of length |file_name_size|, where string pool appears}
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
One can't simply make helter-skelter changes to the following constants,
since certain rather complex initialization
numbers are computed from them. They are defined here using
\.{WEB} macros, instead of being put into \PASCAL's |const| list, in order to
emphasize this distinction.

@d mem_base=0 {smallest index in the |mem| array, must not be less
	than |min_halfword|}
@d hi_mem_base=25000 {smallest index in the single-word area of |mem|,
	must be substantially larger than |mem_base| and smaller than |mem_max|}
@d font_base=0 {smallest internal font number, must not be less
	than |min_quarterword|}
@d hash_size=2100 {maximum number of control sequences; it should be at most
	about |(mem_max-hi_mem_base)/6|, but 2100 is already quite generous}
@d hash_prime=1777 {a prime number equal to about 85\%\ of |hash_size|}
@d hyph_size=307 {another prime; the number of \.{\\hyphenation} exceptions}
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ The ascii code is ``standard'' only to a certain extent, since many
computer installations have found it advantageous to have ready access
to more than 94 printing characters. Appendix@@C of the \TeX\ manual
gives a complete specification of the intended correspondence between
characters and \TeX's internal representation.

The code shown here is intended to be used on the Stanford {\sc SAIL} system,
and at other installations like CMU and ISI where essentially the same
extended character set is used. The fact that {\mc SAIL} has |'}'| in the
wrong place turns out to cause no difficulty in this case.

@<Set initial values...@>=
for i←1 to @'37 do xchr[i]←chr(i);
xchr[@'30]←chr(@'137);
xchr[@'32]←chr(@'33); {|not_equal| sign}
xchr[@'33]←chr(@'176);
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@p function a_open_in(var f:alpha_file):boolean;
	{open a text file for input}
begin reset(f,name_of_file,'/E/O/N:19');
	{the \.{/E} switch distinguishes |form_feed| from |carriage_return|;
	the \.{/O} switch gives error control to us;
	and the \.{/N:19} switch specifies 19 buffers, which is
	supposedly the thing to do at {\mc SAIL}}
a_open_in←not eof(f);
end;
@#
function a_open_out(var f:alpha_file):boolean;
	{open a text file for output}
begin rewrite(f,name_of_file,'/O'); a_open_out←eof(f);
end;
@#
function b_open_in(var f:byte_file):boolean;
	{open a binary file for input}
begin reset(f,name_of_file,'/B:8/O/N:19'); b_open_in←not eof(f);
end;	{the \.{/B} switch is necessary to get byte packing}
@#
function b_open_out(var f:byte_file):boolean;
	{open a binary file for output}
begin rewrite(f,name_of_file,'/O/N:19'); b_open_out←eof(f);
end;	{here we don't pack it since |aryout| is going to be used}
@#
function w_open_in(var f:word_file):boolean;
	{open a word file for input}
begin reset(f,name_of_file,'/O/N:19'); w_open_in←not eof(f);
end;
@#
function w_open_out(var f:word_file):boolean;
	{open a word file for output}
begin rewrite(f,name_of_file,'/O/N:19'); w_open_out←eof(f);
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Input from text files is read one line at a time, using a routine called
|input_ln|. This function is defined in terms of global variables
called |buffer|, |first|, and |last|
that will be  described in detail later; for now, it suffices for us
to know that |buffer| is an array of |ascii_code| values, and that
|first| and |last| are indices into this array representing the
beginning and ending of a line of text.

We will read the lines first into an auxiliary buffer, in order to
save the running time of procedure-call overhead. This uses a nice
feature of \ph\ that I chose not to mention in \TeX82.

At {\mc SAIL} we want to recognize page marks (indicated by |form_feed|
characters), and keep track of the current page number.

@d form_feed=@'14 {ascii code used at end of a page}

@<Glob...@>=
@!buffer:array[0..buf_size] of ascii_code; {lines of characters being read}
@!first:0..buf_size; {the first unused position in |buffer|}
@!last:0..buf_size; {end of the line just input to |buffer|}
@!max_buf_stack:0..buf_size; {largest index used in |buffer|}
@!aux_buf:array[0..70] of text_char; {where the characters go first}
@!tmp_cor_buf:packed array[0..100] of char; {where |tmp_in| puts things}
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@p function input_ln(var f:alpha_file):boolean; {inputs the next line
	or returns |false|}
label 1;
var n: integer;
@!k,@!m: 0..buf_size; {indices into |buffer|}
begin get(f); {input the first character of the line into |f↑|}
if f↑=chr(@'12) then get(f); {skip past a |line_feed|}
if eof(f) then input_ln←false
else	begin last←first;
	read(f,aux_buf:n);
	if buffer[first]=form_feed then {previous line was end-of-page}
		begin incr(page); line←1; {adjust line and page numbers}
		end;
1:	if last+n>max_buf_stack then
		if last+n≥buf_size then
			begin max_buf_stack←buf_size;
			overflow("buffer size",buf_size);
			end
		else max_buf_stack←last+n;
	if n>0 then
		begin m←last;
		if n=72 then last←m+71@+else last←m+n;
		for k←m to last-1 do buffer[k]←xord[aux_buf[k-m]];
		if n=72 then {there's more on this line}
			begin read(f,aux_buf:n); goto 1;
			end;
		end
	else if f↑=chr(form_feed) then {end of page}
		begin aux_buf[0]←f↑; n←1; goto 1;
		end;
	input_ln←true;
	end;
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ The following program does the required initialization
and accepts interrupts and also retrieves a possible command line, using
new system routines due to David R. Fuchs.

@d pto_chr(#)==ptwr1w(0,ord(#)); {put a character in the line editor}

@p procedure esci(var x:integer); extern; @t\2@>@;
	{increments |x| each time the user types escape-I or break-I;
	the program can change |x| whenever it wants to, but |x| had
	better be a global variable}
@#
function rescan:boolean; extern; @t\2@>@;
	{puts the command line into the terminal buffer,
	or returns |false| if there was no command line}
@#
function tmp_in(f:s@&t@&r@&i@&n@&g;var s:s@&t@&r@&i@&n@&g):integer; extern;
	@t\2@>@;
	{reads TMPCOR file |f| into |s|, and returns its length
		(|≤0| means error)}
@#
function cclsw: boolean; extern; @t\2@>@;
	{was program started with \.{RUN} offset of 1 (i.e., from \.{SNAIL})?}
@#
procedure ptwr1w(pty,c:integer); extern; @t\2@>@;
	{simulates typing of a character on a PTY}
@#
function init_terminal:boolean; {gets the terminal files started}
label exit;
var l:integer; {length returned by |tmp_in|}
@!line_found:boolean; {have we scanned a line?}
begin t_open_in;
esci(interrupt);
last←first;
if cclsw then {started by \.{TEX} monitor command}
	begin l←tmp_in('TEX',tmp_cor_buf);
	loc←1;
	while (loc<l)∧(tmp_cor_buf[loc]≠'←') do incr(loc);
	incr(loc);
	while loc<l do
		begin if tmp_cor_buf[loc]>' ' then
			begin buffer[last]←xord[tmp_cor_buf[loc]]; incr(last);
			end;
		incr(loc);
		end;
	end
else
debug if false then@;@+gubed@;@/
if rescan then
	begin read_ln(term_in); {get first character into |term_in↑|}
	while (¬ eoln(term_in))∧(term_in↑≠';') do get(term_in);
	if term_in↑=';' then
		begin get(term_in);
		while ¬ eoln(term_in) do
			begin buffer[last]←xord[term_in↑]; incr(last); get(term_in);
			end;
		end;
	end;
line_found←(last>first);
loop@+	begin loc←first;
	while (loc<last)∧(buffer[loc]=" ") do incr(loc);
	if loc<last then
		begin init_terminal←true;
		return; {return unless the line was all blank}
		end;
	if line_found then
		write_ln(term_out,'Please type the name of your input file.');
	write(term_out,'**'); update_terminal;
@.**@>
	buffer[first]←0; {|input_ln| may look at |buffer[first]|}
	if not input_ln(term_in) then {this shouldn't happen}
		begin write_ln(term_out);
		write(term_out,'! End of file on the terminal... why?');
@.End of file on the terminal@>
		init_terminal←false; return;
		end;
	line_found←true;
	end;
exit:end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@<Character |k| cannot be printed@>=
	k in [0,@'11..@'15,@'33]
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@p procedure term_input; {gets a line from the terminal}
var k:0..buf_size; {index into |buffer|}
begin update_terminal; {Now the user sees the prompt for sure}
buffer[first]←0; {makes sure |input_ln| doesn't find a |form_feed|}
if not input_ln(term_in) then fatal_error("End of file on the terminal!");
@.End of file on the terminal@>
term_offset←0; {the user's line ended with carriage return}
decr(selector); {prepare to echo the input}
if last≠first then for k←first to last-1 do print(buffer[k]);
print_ln; incr(selector); {restore previous status}
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ It is desirable to provide an `\.E' option here that gives the user
an easy way to return from \TeX\ to the system editor, with the offending
line ready to be edited. The present implementation does this by loading
the line editor with the appropriate call to the editor. We treat `\.T' the
same as `\.E', because other programs on this system invoke the editor
when the user says `\.T'.

There is a secret `\.D' option available when the debugging routines have
not been commented out.
@↑debugging@>

@<Interpret code |c| and |return| if done@>=
case c of
"1","2","3","4","5","6","7","8","9": if deletions_allowed then
	@<Delete |c-"0"| tokens, |goto continue|@>;
@t\4\4@>@;@+debug "D": begin debug_help; goto continue;@+end;@+gubed@/
"E","T": if base_ptr>0 then
	begin selector←new_string;
	print("et "); print(input_stack[base_ptr].name_field);
	print_char("/"); print_int(page); print("p/");
	print_int(line); print_char("l");
	if str_ptr<max_strings then
		begin pseudo_typein←str_ptr; incr(str_ptr);
		str_start[str_ptr]←pool_ptr;
		end; {|make_string| not declared |forward|}
	selector←term_and_log; interaction←scroll_mode; jump_out;
	end;
"H": @<Print the help information, |goto continue|@>;
"I":@<Introduce new material from the terminal and |return|@>;
"Q","R","S":@<Change the interaction level and |return|@>;
"X":begin interaction←scroll_mode; jump_out;
	end;
othercases do_nothing
endcases;@/
@<Print the menu of available options@>
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
It is usually most efficient to have |min_quarterword=min_halfword=0|,
so one should try to achieve this unless it causes a severe problem.
The values defined here are recommended for most 36-bit computers.

@d min_quarterword=0 {smallest allowable value in a |quarterword|}
@d max_quarterword=511 {largest allowable value in a |quarterword|}
@d min_halfword==0 {smallest allowable value in a |halfword|}
@d max_halfword==262143 {largest allowable value in a |halfword|}

@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
The inner loop of \TeX\ will run faster with respect to compilers
that don't optimize expressions like `|x+0|' and `|x-0|', if these
macros are simplified in the obvious way when |min_quarterword=0|.
So they have been simplified here in the obvious way.
@↑inner loop@>

@d qi(#)==# {to put an |eight_bits| item into a quarterword}
@d qo(#)==# {to take an |eight_bits| item from a quarterword}
@↑changes for {\mc SAIL}@>
@z
ch_code(carriage_return)←car_ret; ch_code(" ")←spacer; ch_code("\")←escape;
ch_code(form_feed)←car_ret;
ch_code(invalid_code)←invalid_char; ch_code(null_code)←ignore;
for k←"A" to "Z" do
	begin ch_code(k)←letter; ch_code(k+"a"-"A")←letter;@/
	math_code(k)←k+@'70400; math_code(k+"a"-"A")←k+"a"-"A"+@'70400;@/
	lc_code(k)←k+"a"-"A"; lc_code(k+"a"-"A")←k+"a"-"A";@/
	uc_code(k)←k; uc_code(k+"a"-"A")←k;@/
	sf_code(k)←999;
	end;
@↑changes for {\mc SAIL}@>
@z
@ The following procedure, which is called just before \TeX\ initializes its
input and output, establishes the initial values of the date and time.
It uses a {\mc WAITS} monitor call that puts the date in the left 18 bits
and the time in the right 18 bits.

@p procedure fix_date_and_time;
var t:integer; {accumulator}
date:integer; {raw date}
g:boolean; {garbage}
begin calli(@'400101,,t,t,g); {that's {\mc ACCTIM}}
date←t div @'1000000;
time←(t mod @'1000000) div 60;
day←(date mod 31)+1;
month←((date div 31) mod 12)+1;
year←(date div (31*12))+1964;
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
The global variable |line| contains the line number in the topmost
open file, for use in error messages. If we are not reading from
the terminal, |line_stack[index]| holds the line number for the
enclosing level, so that |line| can be restored when the current
file has been read.

Similarly, we maintain a global variable |page| and a corresponding
|page_stack|.

@d terminal_input==(name=0) {are we reading from the terminal?}
@d cur_file==input_file[index] {the current |alpha_file| variable}

@<Globals...@>=
@!in_open : 0..max_in_open; {the number of lines in the buffer, less one}
@!input_file : array[1..max_in_open] of alpha_file;
@!line : integer; {current line number in the current source file}
@!line_stack : array[0..max_in_open] of integer;
@!page : integer; {current page number in the current source file}
@!page_stack : array[0..max_in_open] of integer;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@<Print location of current line@>=
if name≤16 then
	if terminal_input then
		if base_ptr=0 then print_nl("<*>") else print_nl("<insert> ")
	else	begin print_nl("<read "); print_int(name-1); print_char(">");
		end
else	begin if page>1 then
		begin print_nl("p."); print_int(page); print(",l.");
		end
	else print_nl("l.");
	print_int(line);
	end;
print_char(" ")
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ The |begin_file_reading| procedure starts a new level of input for lines
of characters to be read from a file, or as an insertion from the
terminal. It does not take care of opening the file, nor does it set |loc|
or |limit| or |line| or |page|.

@p procedure begin_file_reading;
begin if in_open=max_in_open then overflow("text input levels",max_in_open);
if first=buf_size then overflow("buffer size",buf_size);
incr(in_open); push_input; index←in_open;
line_stack[index]←line; start←first; state←mid_line;
name←0; {|terminal_input| is now |true|}
page_stack[index]←page;
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Conversely, the variables must be downdated when such a level of input
is finished:

@p procedure end_file_reading;
begin first←start; page←page_stack[index]; line←line_stack[index];
if name>16 then a_close(cur_file); {forget it}
pop_input; decr(in_open);
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ If the user has set the |pausing| parameter to some nonzero value,
and if nonstop mode has not been selected,
each line of input is displayed in the transcript file, followed by `\.{=>}',
and also put into the user's line-editor buffer.
\TeX\ waits for the line to be edited, and the next line received is
used instead of the line in the file.

@p procedure firm_up_the_line;
var k:0..buf_size; {an index into |buffer|}
begin limit←last;
if (pausing≠0)∧(interaction>nonstop_mode)∧(buffer[start]≠form_feed) then
	begin print_ln;
	if start=limit then {empty line will be made nonempty so that it's visible}
		begin buffer[start]←" "; incr(limit);
		end;
	decr(selector); {inhibit terminal output temporarily}
	for k←start to limit-1 do
		begin print_char(buffer[k]);
		pto_chr(xchr[buffer[k]]);
		end;
	print("=>"); first←start;
	if not input_ln(term_in) then fatal_error("End of file on the terminal!");
@.End of file on the terminal@>
	if last>first then for k←first to last-1 do print_char(buffer[k]);
	limit←last; print_ln; incr(selector);
	end;
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ The first line of a file must be treated specially, since |input_ln|
starts with |get|. Furthermore we want to omit the optional file
directory page present at {\mc SAIL}.
@↑system dependencies@>

@<Input the first line of |read_file[n]|@>=
begin if eoln(read_file[n]) then last←start
else	begin buffer[start]←xord[read_file[n]↑]; first←start+1;
	if ¬ input_ln(read_file[n]) then confusion("read");
@:confusion read}{\quad read@>
	if(last-start=29)∧(buffer[start]="C")∧(buffer[start+8]=@'26) then
		begin while (read_file[n]↑≠chr(form_feed))∧(not eof(read_file[n])) do
			begin read_ln(read_file[n]); read(read_file[n],aux_buf:temp_ptr);
			end; {skip the directory}
		buffer[start]←form_feed; last←start+1;
		end;
	end;
read_open[n]←normal;
end
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ The file names we shall deal with have the following structure:
If the name contains `\.[', the file area consists of all characters
from this character to the end; otherwise the file area is null.
If the remaining file name contains `\..', the file extension consists of all
such characters from this character to the end, otherwise the file extension
is null. We can assume that there is at most one `\.[' and at most one `\..'.

We can scan such file names easily by using two global variables that keep track
of the occurrences of area and extension delimiters:

@<Glob...@>=
@!area_delimiter:pool_pointer; {the most recent `\.[', if any}
@!ext_delimiter:pool_pointer; {the relevant `\..', if any}
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Input files that can't be found in the user's area may appear in a standard
system area called |TEX_area|. Font files whose areas are not given explicitly
are assumed to appear in a standard system area called |TEX_font_area|.
These system area names will, of course, vary from place to place.

@d TEX_area=="[tex,sys]"
@d TEX_font_area=="[tex,sys]"
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ And here's the second.

@p function more_name(c:ascii_code):boolean;
begin if c=" " then more_name←false
else	begin if c="[" then area_delimiter←pool_ptr
	else if c="." then ext_delimiter←pool_ptr;
	str_room(1); append_char(c); {contribute |c| to the current string}
	more_name←true;
	end;
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ The third.

@p procedure end_name;
begin if str_ptr+3>max_strings then overflow("number of strings",max_strings);
cur_name←str_ptr;
if ext_delimiter=0 then cur_ext←""
else	begin incr(str_ptr);
	str_start[str_ptr]←ext_delimiter; cur_ext←str_ptr;
	end;
if area_delimiter≤str_start[str_ptr] then
	begin cur_area←""; incr(str_ptr); str_start[str_ptr]←pool_ptr;
	end
else	begin incr(str_ptr);
	str_start[str_ptr]←area_delimiter; cur_area←make_string;
	end;
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Conversely, here is a routine that takes three strings and prints a file
name that might have produced them. (The routine is system dependent, because
some operating systems put the file area last instead of first.)

@<Basic printing...@>=
procedure print_file_name(@!n,@!a,@!e:str_number);
begin print(n); print(e); print(a);
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Another system-dependent routine is needed to convert three \TeX\ strings
into the |name_of_file| value that is used to open files.

A special convention is used here with respect to font metric files: If the
file name is longer than six characters (e.g., `\.{helvetica}' or
`\.{oldenglish}'), we abbreviate it by retaining the first three and
last three characters (e.g., `\.{helica}' or `\.{oldish}').

@d append_to_name(#)==begin c←#; incr(k);
	if (c≥"a")∧(c≤"z") then c←c-@'40; {convert to upper case}
	if k≤file_name_size then name_of_file[k]←xchr[c];
	end

@p procedure pack_file_name(@!n,@!a,@!e:str_number);
var k:integer; {number of positions filled in |name_of_file|}
@!c: ascii_code; {character being packed}
@!j:pool_pointer; {index into |string_pool|}
begin k←0;
if (e=".tfm")∧(length(n)>6) then
	begin for j←str_start[n] to str_start[n]+2 do
		append_to_name(str_pool[j]);
	for j←str_start[n+1]-3 to str_start[n+1]-1 do
		append_to_name(str_pool[j]);
	end
else for j←str_start[n] to str_start[n+1]-1 do append_to_name(str_pool[j]);
for j←str_start[e] to str_start[e+1]-1 do append_to_name(str_pool[j]);
for j←str_start[a] to str_start[a+1]-1 do append_to_name(str_pool[j]);
if k≤file_name_size then name_length←k@+else name_length←file_name_size;
for k←name_length+1 to file_name_size do name_of_file[k]←' ';
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ A messier routine is also needed, since format file names must be scanned
before \TeX's string mechanism has been initialized. We shall use the
global variable |TEX_format_default| to supply the text for default system
areas and extensions related to format files.

@d format_default_length=18 {length of the |TEX_format_default| string}
@d format_area_length=9 {length of its area part}
@d format_ext_length=4 {length of its `\.{.fmt}' part}

@<Glob...@>=
@!TEX_format_default:packed array[1..format_default_length] of char;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ @<Set init...@>=
TEX_format_default←'PLAIN.fmt[tex,sys]';
@.PLAIN@>
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Here is the messy routine that was just mentioned. It sets |name_of_file|
from the first |n| characters of |TEX_format_default|, followed by
|buffer[a..b]|, followed by the last |format_ext_length| characters of
|TEX_format_default|; but it actually switches stuff around to keep the
file area last.

We dare not give error messages here, since \TeX\ calls this routine before
the |error| routine is ready to roll. Instead, we simply drop excess characters,
since the error will be detected in another way when a strange file name
isn't found.

@p procedure pack_buffered_name(@!n:small_number;@!a,@!b:integer);
var k:integer; {number of positions filled in |name_of_file|}
@!c: ascii_code; {character being packed}
@!j:integer; {index into |buffer| or |TEX_format_default|}
@!d:integer; {a kludge}
begin if n+b-a+1+format_ext_length>file_name_size then
	b←a+file_name_size-n-1-format_ext_length;
k←0;
for j←a to b do append_to_name(buffer[j]);
if b=0 then
	begin d←format_default_length-format_area_length+1;
	n←format_default_length;
	end
else d←format_default_length-format_area_length-format_ext_length+1;
for j←d to format_default_length-format_area_length do
	append_to_name(xord[TEX_format_default[j]]);
for j←format_default_length-n+1 to format_default_length do
	append_to_name(xord[TEX_format_default[j]]);
if k≤file_name_size then name_length←k@+else name_length←file_name_size;
for k←name_length+1 to file_name_size do name_of_file[k]←' ';
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Operating systems often make it possible to determine the exact name (and
possible version number) of a file that has been opened. The following routine,
which simply makes a \TeX\ string from the value of |name_of_file|, should
ideally be changed to deduce the full name of file@@|f|,
if it is possible to do this in a \PASCAL\ program.

Here is how Dave Fuchs taught \TeX\ to do this on {\mc WAITS}:
@↑Fuchs, David Raymond@>

@p procedure cur_nam(var chan:f@&i@&l@&e;var s:string); extern; @t\2@>@/
function make_name_string(var f:f@&i@&l@&e):str_number;
var s:packed array[1..24] of char;
@!k:1..24; {file names at {\sc SAIL} have at most 23 characters}
begin cur_nam(f,s); str_room(24); k←1;
while ord(s[k])≠0 do
	begin append_char(xord[s[k]]); incr(k);
	end;
make_name_string←make_string;
end;
function a_make_name_string(var f:alpha_file):str_number;
begin a_make_name_string←make_name_string(f);
end;
function b_make_name_string(var f:byte_file):str_number;
begin b_make_name_string←make_name_string(f);
end;
function w_make_name_string(var f:word_file):str_number;
begin w_make_name_string←make_name_string(f);
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Here we have to remember that the |input_ln| routine
starts with a |get|. Also, we want to skip past the editor's directory
page if it is present.

@<Read the first line...@>=
begin if eoln(cur_file) then last←start
else	begin buffer[start]←xord[cur_file↑]; first←start+1;
	if ¬ input_ln(cur_file) then confusion("input");
@:confusion input}{\quad input@>
	if(last-start=29)∧(buffer[start]="C")∧(buffer[start+8]=@'26) then
		begin while (cur_file↑≠chr(form_feed))∧(not eof(cur_file)) do
			begin read_ln(cur_file); read(cur_file,aux_buf:temp_ptr);
			end; {skip the directory}
		buffer[start]←form_feed; last←start+1;
		end;
	end;
firm_up_the_line;
buffer[limit]←carriage_return; first←limit+1; loc←start; line←1; page←1;
end
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@!dvi_index=0..dvi_buf_size; {an index into the output buffer}
@!packed_bytes=packed array[dvi_index] of eight_bits;
	{buffer for \.{DVI} output}
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Some systems may find it more efficient to make |dvi_buf| a |packed|
array, since output of four bytes at once may be facilitated.

@<Glob...@>=
@!dvi_buf:packed_bytes; {buffer for \.{DVI} output}
@!half_buf:dvi_index; {half of |dvi_buf_size|}
@!dvi_limit:dvi_index; {end of the current half buffer}
@!dvi_ptr:dvi_index; {the next available buffer address}
@!dvi_offset:integer; {|dvi_buf_size| times the number of times the
	output buffer has been emptied}
@!dvi_gone:integer; {the number of bytes already output to |dvi_file|}
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ The actual output of |dvi_buf[a..b]| to |dvi_file| is performed by calling
|write_dvi(a,b)|. For best results, this procedure should be optimized to
run as fast as possible on each particular system, since it is part of
\TeX's inner loop. It is safe to assume that |a| and |b+1| will both be
multiples of 4 when |write_dvi(a,b)| is called; therefore it is possible on
many machines to use efficient methods to pack four bytes per word and to
output an array of words with one system call.
@↑inner loop@>

@p procedure ary_out(var f:file;@!b:packed_bytes; @!o,@!c:integer);
	extern;@t\2@>@#
procedure write_dvi(@!a,@!b:dvi_index);
begin ary_out(dvi_file,dvi_buf,a div 4,(b+1-a)div 4);
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@<Finish the \.{DVI} file@>=
if total_pages=0 then print_nl("No output file.")
@.No output file@>
else	begin dvi_out(post); {beginning of the postamble}
	dvi_four(last_bop); last_bop←dvi_offset+dvi_ptr-5; {|post| location}
	dvi_four(25400000); dvi_four(473628672); {conversion ratio for sp}
	prepare_mag; dvi_four(mag); {magnification factor}
	dvi_four(max_v); dvi_four(max_h);@/
	dvi_out(max_push div 256); dvi_out(max_push mod 256);@/
	dvi_out(total_pages div 256); dvi_out(total_pages mod 256);@/
	@<Output the font definitions for all fonts that were used@>;
	dvi_out(post_post); dvi_four(last_bop); dvi_out(id_byte);
	k←4+((dvi_buf_size-dvi_ptr) mod 4); {the number of 223's}
	while k>0 do
		begin dvi_out(223); decr(k);
		end;
	@<Empty the last bytes out of |dvi_buf|@>;
	print_nl("Output written on "); print(output_file_name);
@.Output written on x@>
	print(" ("); print_int(total_pages); print(" page");
	if total_pages≠1 then print_char("s");
	print(", "); print_int(dvi_offset+dvi_ptr); print(" bytes).");
	b_close(dvi_file);
	if pseudo_typein=0 then
		begin k←selector; selector←new_string;
		print("r DVIdover;"); print(output_file_name); 
		selector←k; str_room(1);
		pseudo_typein←make_string;
		end;
	end
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ @<Finish issuing a diagnostic message for an overfull or underfull hbox@>=
if output_active then print(") has occurred while \output is active")
else	begin if par_begin_line≠0 then
		begin print(") in paragraph at lines "); print_int(par_begin_line);
		print("--");
		end
	else print(") detected at line ");
	print_int(line);
	if page>1 then
		begin print(", p."); print_int(page);
		end;
	end;
print_ln;@/
font_in_short_display←undefined_font; short_display(list_ptr(r)); print_ln;@/
begin_diagnostic; show_box(r); end_diagnostic(true)
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ @<Finish issuing a diagnostic message for an overfull or underfull vbox@>=
if output_active then print(") has occurred while \output is active")
else	begin print(") detected at line "); print_int(line);
	if page>1 then
		begin print(", p."); print_int(page);
		end;
	print_ln;@/
	end;
begin_diagnostic; show_box(r); end_diagnostic(true)
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@ Here we do whatever is needed to complete \TeX's job gracefully
on the local operating system.

The new stuff at {\mc SAIL} has to do with preparing for what the user
presumably wants to do next, by typing it for him/her.

@<Last-minute...@>=

procedure close_files_and_terminate;
var k:integer; {all-purpose index}
begin @<Finish the extensions@>;
stat if tracing_stats≠0 then @<Output statistics about this job@>;@;@+tats@/
@<Finish the \.{DVI} file@>;
if job_name>0 then
	begin write_ln(log_file); a_close(log_file);
	end;
if (pseudo_typein≠0)∧(interaction>batch_mode) then
	begin write_ln(term_out);
	for k←str_start[pseudo_typein] to str_start[pseudo_typein+1]-1 do
		pto_chr(xchr[str_pool[k]]);
	end;
end;
@↑system dependencies@>@↑changes for {\mc SAIL}@>
@z
@* \[54] System-dependent changes.
Here are the remaining things needed to make the implementation
complete at {\mc SAIL}.
@↑system dependencies@>@↑changes for {\mc SAIL}@>

@ The |pseudo_typein| variable is set nonzero if the |error| routine
uses the `\.E' option to exit and edit.

@<Glob...@>=
@!pseudo_typein:str_number;

@ @<Set init...@>=
pseudo_typein←0; page←0;
@z